package proxy

import (
	"bytes"
	"context"
	"crypto/tls"
	"encoding/binary"
	"errors"
	"gitee.com/filters/start/safe_map"
	"github.com/spf13/cast"
	"golang.org/x/net/http2"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
)

type Client struct {
	client *http.Client
	body   *http.Request
}

func NewClient() *Client {
	c := &Client{
		client: &http.Client{
			Transport: &http2.Transport{
				AllowHTTP: true,
				DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
					return net.Dial(network, addr)
				},
			},
		},
		body: &http.Request{
			Method: "POST",
			URL: &url.URL{
				Scheme: "https",
			},
			Header: http.Header{
				"content-type": []string{"application/grpc+json"},
			},
		},
	}
	return c
}

func (c *Client) Post(host, path string, data []byte) ([]byte, error) {
	c.body.URL.Host = host
	c.body.URL.Path = path
	if len(data) <= 0 {
		data = []byte("{}")
	}
	c.body.Body = ioutil.NopCloser(bytes.NewReader(c.PrepData(data)))
	r, err := c.client.Do(c.body)
	if err != nil {
		return nil, err
	}
	if r.StatusCode != http.StatusOK {
		return nil, err
	}
	defer r.Body.Close()

	if r.Header.Get("Grpc-Status") != "" && r.Header.Get("Grpc-Status") != "0" {
		return nil, errors.New(r.Header.Get("Grpc-Message"))
	}

	contents, err := io.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	if len(contents) < 5 {
		return contents, nil
	}
	return contents[5:], nil
}

func (c *Client) PostWithMetadata(host, path string, data []byte, md safe_map.ConfigSafeMap) ([]byte, error) {
	c.body.URL.Host = host
	c.body.URL.Path = path
	if len(data) <= 0 {
		data = []byte("{}")
	}
	c.body.Body = ioutil.NopCloser(bytes.NewReader(c.PrepData(data)))
	// 添加metadata到请求头
	for k, v := range md.Data {
		c.body.Header.Set(k, cast.ToString(v))
	}
	// 添加grpc特定的metadata前缀
	if _, ok := c.body.Header["Grpc-Metadata"]; !ok {
		c.body.Header.Set("Grpc-Metadata", "true")
	}
	r, err := c.client.Do(c.body)
	if err != nil {
		return nil, err
	}
	if r.StatusCode != http.StatusOK {
		return nil, err
	}
	defer r.Body.Close()

	if r.Header.Get("Grpc-Status") != "" && r.Header.Get("Grpc-Status") != "0" {
		return nil, errors.New(r.Header.Get("Grpc-Message"))
	}

	contents, err := io.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	if len(contents) < 5 {
		return contents, nil
	}
	return contents[5:], nil
}

func (c *Client) PrepData(data []byte) []byte {
	compAndLen := make([]byte, 5)
	compAndLen[0] = 0
	binary.BigEndian.PutUint32(compAndLen[1:], uint32(len(data)))
	return append(compAndLen, data...)
}
